import type { Plugin } from 'vite'
import type { PageMetaDatum, SubPageMetaDatum, UserPagesConfig } from './config'
import type { IGenDeclaration } from './declaration'
import fs from 'node:fs'
import path, { normalize } from 'node:path'
import chokidar from 'chokidar'
import { createFile } from './create-file'
import { genDeclaration } from './declaration'
import { genMainConfig, parseModulesToJsonWithJSON5 } from './utils'

function checkFileExists(path: string) {
  try {
    return fs.existsSync(path)
  }
  catch (error) {
    return false
  }
}
let gConfig: { pages: PageMetaDatum[], subPackages?: SubPageMetaDatum[] } | null = null
let pageMainConfig = {} as UserPagesConfig
let pageConfigAst = {} as any

class PluginLogic {
  nodeEnv?: 'development' | 'production'
  moduleDir!: string
  pagesPath!: string
  rootDir!: string
  notFoundTip!: boolean

  pages: Record<string, PageMetaDatum[]> = {}
  subPackages: Record<string, SubPageMetaDatum> = {}
  modules: string[] = []
  modulesMap: Record<string, string> = {}
  autoCreateFile!: boolean
  pagesDir!: string
  watcher: any
  disabled!: boolean
  template!: string
  suffix!: 'js' | 'ts'
  pagesDts!: string
  constructor({ disabled, autoCreateFile, pagesDir, suffix, notFoundTip, moduleDir, pagesPath, root, template = '', pagesDts = '' }: Record<string, any>) {
    if (disabled) {
      this.unWatch()
      return
    }
    this.template = template
    this.notFoundTip = notFoundTip
    this.autoCreateFile = autoCreateFile
    this.suffix = suffix
    this.pagesDir = path.join(root, pagesDir)
    this.moduleDir = path.join(root, moduleDir)
    this.pagesPath = path.join(root, pagesPath)
    this.pagesDts = path.join(root, pagesDts)

    this.rootDir = root
    this.watcher = null
    // 监听配置目录文件变更,重新组装文件,触发差量编译
    this.watchDirChange()
  }

  watchDirChange() {
    if (this.watcher) return
    const watcher = this.watcher = chokidar.watch(this.moduleDir, {
      persistent: true, // 保持监控状态
      ignoreInitial: true, // 初始扫描时触发事件
      depth: 1, // 监控目录的深度
      interval: 1000, // 轮询间隔（毫秒）
    })
    watcher.on('add', (path: string) => {
      this.add(path)
    }).on('change', (path: string) => {
      this.update(path)
    }).on('unlink', (path: string) => {
      this.unlink(path)
    }).on('unlinkDir', (path: string) => {
      this.unlink(path)
    })
  }

  unWatch() {
    this.watcher && this.watcher.unwatch(this.moduleDir)
  }

  add(p: string) {
    // 新增删除频率较低，先不做差量更新优化
    this.process()
  }

  async update(p: string) {
    // 读取主配置
    if (p === path.join(this.moduleDir, `index.${this.suffix}`)) {
      await this.readMain()
      if (pageMainConfig.disabled) return
      const modulesPath = this.filterPathByMainConfig()
      await this.parseModules(modulesPath)
      return
    }
    if (this.modulesMap[p]) {
      await this.parseModules([p])
    }
  }

  unlink(p: string) {
    // 新增删除频率较低，先不做差量更新优化
    this.process()
  }

  async process() {
    // 读取主配置
    await this.readMain()// 读取主配置
    if (pageMainConfig.disabled) return
    this.createFilePathMap()
    const modulesPath = this.filterPathByMainConfig()
    await this.parseModules(modulesPath)
  }

  createFilePathMap() { // 读取路由模块下的所有文件
    const modulesMap: Record<string, string> = {}
    const temp = []
    const modules = fs.readdirSync(this.moduleDir, 'utf-8')
    for (let i = 0; i < modules.length; i++) {
      const p = modules[i]
      const abs = path.join(this.moduleDir, p)
      const stat = fs.lstatSync(abs)
      if (stat.isDirectory()) {
        const file = path.join(abs, `index.${this.suffix}`)
        const exist = checkFileExists(file)
        if (exist) {
          modulesMap[p] = file
          modulesMap[file] = p
          temp.push(p)
        }
      }
    }

    this.modules = temp // [ 'page-tabbar', 'pages-demo', 'pages-user' ]
    // 模块路径所在文件映射
    this.modulesMap = modulesMap // {'page-tabbar': 'c://***\\page-tabbar\\index.ts', 'pages-demo': 'c://***\\pages-demo\\index.ts', 'pages-user': 'c://***\\pages-user\\index.ts'}
  }

  filterPathByMainConfig(): string[] {
    const modulesPath: string[] = []

    const addPath = (p: string) => modulesPath.push(this.modulesMap[p])
    const test1 = !pageMainConfig.include && !pageMainConfig.exclude
    const test2 = pageMainConfig.include && pageMainConfig.exclude
    // 获取exclude的差集
    const intersection = pageMainConfig.include?.filter((item: string) => !pageMainConfig.exclude!.includes(item)) ?? []

    for (let i = 0; i < this.modules.length; i++) {
      const m = this.modules[i]
      if (test1) {
        addPath(m)
      }
      else if (test2) {
        if (intersection.length === 0) {
          addPath(m)
          continue
        }
        if (intersection.length > 0 && intersection.includes(m)) addPath(m)
      }
      else if (pageMainConfig.include) {
        if (pageMainConfig.include.includes(m) || pageMainConfig.include.length === 0) addPath(m)
      }
      else if (pageMainConfig.exclude) {
        if (!pageMainConfig.exclude.includes(m) || pageMainConfig.exclude.length === 0) addPath(m)
      }
    }
    return modulesPath
  }

  async readMain() {
    const configPath = path.join(this.moduleDir, `index.${this.suffix}`)
    const readConfig = await genMainConfig(configPath)

    pageConfigAst = readConfig.ast
    pageMainConfig = readConfig.config
  }

  async parseModules(modulesPath: string[]) {
    try {
      const homePagePath = pageMainConfig.homePage
      const { json, json5 } = await parseModulesToJsonWithJSON5({
        notFoundTip: this.notFoundTip,
        pageConfigAst,
        modulesPath,
        homePagePath,
        pageVisit: (pagePath) => {
          const filePath = path.join(this.pagesDir, `${pagePath}.vue`)

          if (this.notFoundTip && !checkFileExists(filePath)) {
            console.log(`文件路径${pagePath}.vue不存在`)

            this.autoCreateFile && createFile(filePath, this.template)
          }
        },
        subPageVisit: ({ fullPath, root, subPath }) => {
          const filePath = path.join(this.pagesDir, `${fullPath}.vue`)

          if (this.notFoundTip && !checkFileExists(filePath)) {
            console.log(`分包${root}-->${subPath}.vue不存在`)

            this.autoCreateFile && createFile(filePath, this.template)
          }
        }
      })
      gConfig = json

      this.writeJSON5(json5)
    }
    catch (error) {
      console.log(error)
    }
  }

  writeJSON5(json5: string) {
    delete pageMainConfig.include
    delete pageMainConfig.exclude
    delete pageMainConfig.homePage

    const pagesConfig = {
      ...gConfig,
      ...pageMainConfig
    } as IGenDeclaration

    fs.writeFileSync(this.pagesPath, json5, { flag: 'w' })
    console.log(`pages.json文件已更新`)

    genDeclaration(pagesConfig, this.pagesDts)
  }
}
interface IRouteConfig {
  /** @description  是否禁用插件 */
  disabled?: string
  /** @description  路由模块文件后缀，默认ts */
  suffix?: 'ts' | 'js'
  /** @description  根目录 默认取process.cwd() */
  root?: string
  /** @description  没有相应的文件是否提示 */
  notFoundTip?: boolean
  /** @description  路由模块路径,相对于root根目录，默认为root根目录下./src/router/modules */
  moduleDir?: string
  /** @description  pages.json 文件路径，如hbuilderx下 ./pages.json, cli下为./src/pages.json，默认为cli下的./src/pages.json */
  pagesPath?: string
  /** @description  是否根据路由自动创建没有的页面文件 */
  autoCreateFile?: boolean
  /** @description  页面文件路径，相对于root根目录，如默认为./src，则会根据路由文件的path在./src下创建文件 */
  pagesDir?: string
  /** @description  自定义页面模板 */
  template?: string
  /** @description  自定义页面模板 */
  pagesDts?: string
}
const virtualModuleId = 'virtual:mui-uni-pages'
const resolvedVirtualModuleId = `\0${virtualModuleId}`
export function uniRoute(userOptions: IRouteConfig = {}): Plugin {
  // eslint-disable-next-line node/prefer-global/process
  const { disabled = false, autoCreateFile = true, pagesDir = '', suffix = 'ts', template, notFoundTip = true, moduleDir = './router/modules', root = process.cwd(), pagesPath = './src/pages.json', pagesDts = './uni-pages.d.ts' } = userOptions
  const pluginLogic = new PluginLogic({ disabled, autoCreateFile, pagesDir, suffix, notFoundTip, moduleDir, pagesPath, root, template, pagesDts })
  return {
    name: 'vite-plugin-uni-route',
    enforce: 'pre',
    async config() {
      if (disabled) return
      try {
        await pluginLogic.process()
      }
      catch (err) {
        console.log('路由插件报错，请检查pages.json文件是否正确')
        console.log(err)
      }
    },
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },
    async load(id) {
      if (id === resolvedVirtualModuleId) {
        const pages = `export const pages = ${JSON.stringify(gConfig!.pages)};`
        const subPages = `export const subPages = ${JSON.stringify(gConfig!.subPackages)};`
        const pageConfig = `export const pageConfig = ${JSON.stringify(pageMainConfig)};`

        return [pages, subPages, pageConfig].join('\n')
      }
    },
  }
}

export default uniRoute
